At their heart, decorators are functions that take a function as input and return a wrapping function as output
In [3]:
def hello_world():
print('hello world')
# wrap hello world in a function that does logging
def wrap_hello():
print('Enter: hello_world')
hello_world()
print('Exit: hello_world')
wrap_hello()
In [4]:
# to wrap any function at all, write a generic wrapper that takes the a function as input
def logthis(func):
print('Enter: {}'.format(func.__name__))
func()
print('Exit: {}'.format(func.__name__))
logthis(hello_world)
In [5]:
# add the ability to handle arbitrary parameters and return types
def logthis(func):
def wrapper(*args, **kwargs):
print('Enter: {}'.format(func.__name__))
result = func(*args, **kwargs)
print('Exit: {}'.format(func.__name__))
return result
return wrapper # return the inner function
logged_hello = logthis(hello_world)
logged_hello()
In [6]:
# now you can replace hello_world with the wrapped function
hello_world=logged_hello
hello_world()
In [7]:
# the only problem is that function metadata is wrong
hello_world.__name__
Out[7]:
In [8]:
#instead use functools.wraps and a decorator on your function, which otherwise is equivalent
from functools import wraps
def logthis(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Enter: {}'.format(func.__name__))
result = func(*args, **kwargs)
print('Exit: {}'.format(func.__name__))
return result
return wrapper # return the inner function
hello_world=None
@logthis
def hello_world():
print('hello world')
hello_world()
In [9]:
#if you use wraps, you can also access the raw undecorated function using __wrapped__
raw_hello = hello_world.__wrapped__
raw_hello()
In [11]:
# Decorator that takes arguments
def logged(prefix):
prefix = prefix if prefix else ''
logprefix = prefix + ':' if len(prefix) > 0 else prefix
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('{}Enter:{}'.format(logprefix, func.__name__))
result = func(*args, **kwargs)
print('{}Exit:{}'.format(logprefix, func.__name__))
return result
return wrapper
return decorate
@logged('logger')
def hello_world():
print('hello world')
hello_world()
In [13]:
# always declare classmethod and staticmethod decorators first (so they are applied last)
class Spam:
@logthis
def instance_method(self, n):
print(self, n)
while n > 0:
n -= 1
@classmethod
@logthis
def class_method(cls, n):
print(cls, n)
while n > 0:
n -= 1
@staticmethod
@logthis
def static_method(n):
print(n)
while n > 0:
n-=1
s = Spam()
s.instance_method(4)
In [14]:
Spam.class_method(10)
In [16]:
# Singleton metaclass
class Singleton(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance
# Example
class Spam(metaclass=Singleton):
def __init__(self):
print('Creating Spam')
a = Spam() #generates 'Creating Spam'
b=Spam() #no print message produced
b is a
Out[16]:
In [17]:
# Metaclass to cache objects by string provided in ctor
import weakref
class Cached(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionary()
def __call__(self, *args):
if args in self.__cache:
return self.__cache[args]
else:
obj = super().__call__(*args)
self.__cache[args] = obj
return obj
# Example
class Spam(metaclass=Cached):
def __init__(self, name):
print('Creating Spam({!r})'.format(name))
self.name = name
a =Spam('a')
b=Spam('b')
b2 = Spam('b')
a is b
Out[17]:
In [18]:
b is b2
Out[18]: